/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package br.ufu.sdg12014.thriftpluresdb.server;

import br.ufu.sdg12014.base.clientreply.InvalidOperation;
import br.ufu.sdg12014.base.clientreply.ThriftServer;
import br.ufu.sdg12014.base.replycoordinator.ThriftReplica;
import br.ufu.sdg12014.base.replycoordinator.ThriftReplica.Client;
import br.ufu.sdg12014.base.replycoordinator.Transacao;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import org.apache.thrift.TException;
import plures.Core;
import plures.Return;
import plures.transaction.Command;
import plures.transaction.Commands;
import plures.transaction.Transaction;
/**
 *
 * @author SergioPeres
 */
public class ServerHandler implements ThriftServer.Iface, ThriftReplica.Iface{

    private ArrayList<Client>               participants;
    private Core                            core;
    private HashSet<Integer>                tokens;
    private static final String             unAuthError = "[ERROR] Unauthorized";
    private static final String             abortError = "[INFO] COMMIT: Global Abort";
    private static final String             commitOK = "[INFO] COMMIT: Committed";
    private HashMap<Integer,Transaction>    transactions;
    private boolean                         lock = false;
    private static final String             participantsAddress = "serverList.data";
    
    public ServerHandler(String fileName, int dataLength) throws FileNotFoundException, IOException
    {
        this.tokens = new HashSet<>();
        this.core = new Core(fileName, dataLength);
        this.participants = new ArrayList<>();
        BufferedReader br = new BufferedReader(new FileReader(participantsAddress));
        for(String s = br.readLine(); s != null; s = br.readLine())
        {
            Client c = new Client()
        }
    }

    @Override
    public String opCreate(int id, int chave, String valor) throws InvalidOperation, TException
    {
        if(!this.checkAuthorization(id))
        {
            return unAuthError;
        }        
        Return r = core.checkCreate(chave, valor, id);
        if(r.getStatus() == 200)
        {
            ArrayList<Object> args = new ArrayList<>();
            args.add(chave);
            args.add(valor);
            Command c = new Command(Commands.CREATE, args);
            this.transactions.get(id).add(c);
        }
        return r.toString();
    }

    @Override
    public String opRead(int id, int chave) throws InvalidOperation, TException
    {
        if(!this.checkAuthorization(id))
        {
            return unAuthError;
        }
        Return r = core.checkRead(chave, id);
        if(r.getStatus() == 200)
        {
            ArrayList<Object> args = new ArrayList<>();
            args.add(chave);
            Command c = new Command(Commands.READ, args);
            this.transactions.get(id).add(c);
        }
        return chave + " -> " + r.toString();
    }

    @Override
    public String opUpdate(int id, int chave, String valor) throws InvalidOperation, TException
    {
        if(!this.checkAuthorization(id))
        {
            return unAuthError;
        }
        Return r = core.checkUpdate(chave, valor, id);
        if(r.getStatus() == 200)
        {
            ArrayList<Object> args = new ArrayList<>();
            args.add(chave);
            args.add(valor);
            Command c = new Command(Commands.UPDATE, args);
            this.transactions.get(id).add(c);
        }
        return chave + " -> " + r.toString();
    }

    @Override
    public String opDelete(int id, int chave) throws InvalidOperation, TException
    {
        if(!this.checkAuthorization(id))
        {
            return unAuthError;
        }
        Return r = core.checkDelete(chave, id);
        if(r.getStatus() == 200)
        {
            ArrayList<Object> args = new ArrayList<>();
            args.add(chave);
            Command c = new Command(Commands.DELETE, args);
            this.transactions.get(id).add(c);
        }
        return r.toString();
    }
    
    public boolean opBegin(int client) throws InvalidOperation, TException
    {
        if(!this.checkAuthorization(client))
        {
            return false;
        }
        Transaction transaction = new Transaction(client);
        this.transactions.put(client, transaction);
        return true;
    }
    
    public synchronized String opEnd(int client) throws InvalidOperation, TException
    {
        this.lock = true;
        if(!this.checkAuthorization(client))
        {
            return unAuthError;
        }
        Transaction transaction = this.transactions.get(client);
        Transacao transacao = transaction.toTransacao();
        boolean flag = true;
        // vote request
        for(ServerHandler sh : this.participants)
        {
            if(!sh.opRequestVote(client, transacao)) // enviar pedido de voto
            {
                flag = false;
            }
        }
        
        if(!flag)
        {
            for(ServerHandler sh : this.participants)
            {
                sh.opGlobalAbort(client);
            }
            return abortError;
        }
        
        // commit
        for(ServerHandler sh : this.participants)
        {
            sh.opPrepareCommit(client);
        }
        
        for(ServerHandler sh : this.participants)
        {
            sh.opGlobalCommit(client);
        }
        return commitOK; 
    }
    
    private boolean checkAuthorization(int client) throws InvalidOperation, TException
    {
        return this.tokens.contains(client) && this.transactions.containsKey(client);
    }
       
    public boolean opRequestVote(int client, Transacao transacao) throws InvalidOperation, TException
    {
        Transaction transaction = new Transaction(client,transacao);
        if(this.lock)
        {
            return false;
        }
        Commands sig;
        boolean permission = false;
        for(Command cmd : transaction.getCmds())
        {
            sig = cmd.getSignature();
            
            switch(sig)
            {
                case CREATE: permission = this.core.canCreate(cmd.getKey(), cmd.getValue(), client);
                    break;
                case READ: permission = this.core.canRead(cmd.getKey(), client);
                    break; 
                case UPDATE: permission = this.core.canUpdate(cmd.getKey(), client);
                    break;
                case DELETE: permission = this.core.canDelete(cmd.getKey(), client);
                    break;
            }
            if(!permission)
            {
                return false;
            }
        }
        this.lock = true;
        return true;
    }
    
    public synchronized boolean opPrepareCommit(int client) throws InvalidOperation, TException
    {
        return true;
    }
    
    public synchronized boolean opGlobalCommit(int client) throws InvalidOperation, TException
    {
        Commands sig;
        String resp = "";
        Transaction transaction = this.transactions.get(client);
        for(Command cmd : transaction.getCmds())
        {
            sig = cmd.getSignature();
            
            switch(sig)
            {
                case CREATE: resp += this.core.create(cmd.getKey(), cmd.getValue(), client) + "\n";
                    break;
                case READ: resp += this.core.read(cmd.getKey(), client) + "\n";
                    break; 
                case UPDATE: resp += this.core.update(cmd.getKey(), cmd.getValue(), client) + "\n";
                    break;
                case DELETE: resp += this.core.delete(cmd.getKey(), client) + "\n";
                    break;
            }
        }
        this.core.commit();
        this.transactions.remove(client);
        this.lock = false;
        return true;
    }
    
    public boolean opGlobalAbort(int client) throws InvalidOperation, TException
    {
        this.transactions.remove(client);
        return true;
    }
    
    public String opEnter() throws InvalidOperation, TException
    {
        return "none";
    }

    public String opUpdateServerFile(List<String> participants) throws InvalidOperation, TException
    {
        return "none";
    }

    @Override
    public int opGetToken() throws InvalidOperation, TException
    {
        int token;
        do
        {
            token = (int)(10000000.0 * Math.random());
        } while(this.tokens.contains(token));
        this.tokens.add(token);
        return token;
    }
    
}
